package com.pajk.user.service.login;


import com.alibaba.common.lang.StringUtil;
import com.alibaba.fastjson.*;
import com.alibaba.fastjson.serializer.*;
import com.pajk.message.api.*;
import com.pajk.message.model.*;
import com.pajk.user.api.*;
import com.pajk.user.dal.index.*;
import com.pajk.user.dal.mapper.*;
import com.pajk.user.dal.model.*;
import com.pajk.user.exception.*;
import com.pajk.user.model.*;
import com.pajk.user.model.enums.*;
import com.pajk.user.service.common.idpool.*;
import com.pajk.user.service.common.metaq.*;
import com.pajk.user.service.login.common.*;
import com.pajk.user.service.login.question.*;
import com.pajk.user.service.login.sms.*;
import com.pajk.user.service.login.sms.SmsPasswordManager.*;
import com.pajk.user.util.*;
import net.pocrd.core.*;
import net.pocrd.dubboext.*;
import net.pocrd.entity.*;
import net.pocrd.util.*;
import org.slf4j.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.transaction.annotation.*;

import javax.annotation.*;
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class LoginServiceHttpExportImpl implements LoginServiceHttpExport{
	
	private static final Logger Log = LoggerFactory.getLogger(LoginServiceHttpExportImpl.class);
	
    @Resource
    private UserMapper userMapper;
    
    @Resource
    private UserRelationMapper userRelationMapper;
    
    @Resource
    private AccountChannelMapper accountChannelMapper;

    @Resource
    private TairIndex<Long, Long> mobileToUserIdIndex;
    
    @Resource
    UserService userService;
    
    @Resource
    SmsPasswordManager smsPasswordManager;
    
    @Resource
	private SmsService smsService;
	
	@Resource
	UserDeviceBindingMapper userDeviceBindingMapper;
		
    @Autowired
    @Qualifier("userIdPool")
    private IDPool userIdPool;

    @Resource
    LoginService loginService;

    @Resource
    Index<String,HashMap<Long,InviteInfo>> inviteeMobileToInviteInfoIndex;

    @Resource
    private MsgSenderService msgSender;

	@Override
	@Transactional
    public LoginResp renewUserToken(String expiredToken, int appId) {
		try {
			CallerInfo caller = null;
			caller = TokenHelper.getInstance().parse(expiredToken);
            if(caller == null) {
                Log.error("caller cannot be null");
                DubboExtProperty.setErrorCode(UserServiceHttpCode.RENEW_USERTOKEN_FAILED);
                return null;
            }
			if(appId != caller.appid) {
				Log.error("appId not same");
				DubboExtProperty.setErrorCode(UserServiceHttpCode.RENEW_USERTOKEN_FAILED);
				return null;
			}
            // 1. should check if this userId exists;
            User user = userMapper.getUserById(caller.uid);
            if(user == null) {
                Log.error("user cannot be null");
                DubboExtProperty.setErrorCode(UserServiceHttpCode.RENEW_USERTOKEN_FAILED);
                return null;
            }
            // 2. should check if userId <--> deviceId binding exists
            UserDeviceBindingDO userDeviceBindingDO = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(user.getId(),caller.deviceId);
            if(userDeviceBindingDO == null || userDeviceBindingDO.getActive() != 0) {
                Log.error("userDeviceBindingDO is null or user is not active");
                DubboExtProperty.setErrorCode(UserServiceHttpCode.RENEW_USERTOKEN_FAILED);
                return null;
            }
			LoginResp loginResp = new LoginResp();
			loginResp.expire = TokenHelper.generaterExpire();
			loginResp.token = TokenHelper.generateUserToken(caller.appid, caller.deviceId, caller.uid, loginResp.expire,caller.key);
			loginResp.uid = caller.uid;
			return loginResp;
		}catch(RuntimeException t) {
    		Log.error("userToken valid fail", t);
            DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR); 
    	}
		return null;
    }

	@Transactional
    @Override
    public boolean changePassword(long userId, String newPass) {
    	// 1. update new pass
    	if(StringUtil.isEmpty(newPass)) {
    		Log.error("newPass cannot be null");
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		return false;
    	}
    	try {
	    	User user = userMapper.getUserById(userId);
	    	if(user == null) {
	    		Log.error("user cannot be null,userId is {}",userId);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	    		return false;
	    	}
	    	if(newPass.equals(user.getPassword())) {
	    		Log.error("password cannot be same");
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.SAME_PASSWORD_ERROR);
	    	}
	    	Map<String, Object> params = new HashMap<String, Object>();
	        params.put("id", userId);
	        params.put("password", newPass);
	        userMapper.updateUserById(params);
	        return true;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return false;
    }

    @Transactional
    @Override
    public boolean bindMobile(long userId,String mobile) {
    	if(StringUtil.isEmpty(mobile)) {
    		Log.error("mobile cannot be null");
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    	}
    	try {
	    	Long mobileNumber = null;
	    	mobileNumber = Long.valueOf(mobile);
    	
	    	User user = userMapper.getUserById(userId);
	    	if(user == null) {
	    		Log.error("user cannot found, userId is {}", userId);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_AQUIRABLE);
	    	}
	    	// do db and tair update
	    	Map<String, Object> param = new HashMap<String, Object>();
	        param.put("id", userId);
	        param.put("mobile", mobile);
	        userMapper.updateUserById(param);
	        mobileToUserIdIndex.put(mobileNumber,userId);
	        return true;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return false;
    }

    @Transactional
    @Override
    public LoginResp login(String mobile, String deviceToken, String password) {
    	if(StringUtil.isEmpty(mobile) || StringUtil.isEmpty(deviceToken) || StringUtil.isEmpty(password)) {
    		Log.error("paramter cannot be null,mobile is {},deviceToken is {}, password is {}.", mobile,deviceToken,password);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.LOGIN_FAILED);
    		return null;
    	}
    	try {
            if(!PhoneNumberUtil.isMobileNumber(mobile)) {
                Log.error("mobile format fail,mobile is {}",mobile);
                DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
                return null;
            }
	        // 1. mobile --> user id
	        Long userId = mobileToUserIdIndex.get(Long.valueOf(mobile));
	        if(userId == null) {
	        	Log.error("userId cannot found in tair, userId is {}", userId);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        	return null;
	        }
	        // 2. check password
	        User user = userMapper.getUserById(userId);
	        if(user == null) {
	        	Log.error("user cannot found, userId is {}", userId);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        }else if(!password.equals(user.getPassword())) {
	        	Log.error("password error");
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USERNAME_OR_PASSWORD_ERROR);
	        	return null;
	        }
	        // 3. generate user token
	        LoginResp loginResp = new LoginResp();
	        CallerInfo callerInfo = TokenHelper.getInstance().parse(deviceToken);
	        loginResp.expire = TokenHelper.generaterExpire();
	        loginResp.token = TokenHelper.generateUserToken(callerInfo.appid, callerInfo.deviceId, user.getId(), loginResp.expire,callerInfo.key);
	        loginResp.uid =  user.getId();
            // 4. add user-device binding
            // 5. activate this binding
            mergeUserDeviceBinding(callerInfo.deviceId,userId,callerInfo.appid);
	        return loginResp;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return null;
    }

    @Transactional
    @Override
    public boolean logout(long userId, long deviceId) {
    	try {
	    	UserDeviceBindingDO userDeviceBinding = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(userId, deviceId);
	    	if(userDeviceBinding == null) {
	    		Log.error("userDeviceBinding cannot be null,userId and deviceId are {}",userId+" "+deviceId);
	    		return false;
	    	}
	    	User user = userMapper.getUserById(userId);
	    	if(user.getPassword() == null || "".equals(user.getPassword())) {
	    		Log.error("password not set");
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.STATIC_PASSWORD_NOT_SET);
	    		return false;
	    	}
	    	Map<String, Object> param = new HashMap<String, Object>();
	        param.put("userId", userId);
	        param.put("deviceId", deviceId);
	        param.put("active", 1);//set active  is  inactive
	        userDeviceBindingMapper.updateUserDeviceBindingByUserIdAndDeviceId(param);
	        return true;
    	}catch(ServiceException e) {
    		throw e;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return false;
        
    }

    @Transactional
    @Override
    public boolean resetPassword(long mobile, String newPass) {
        // 1. update new pass
    	if(StringUtil.isEmpty(newPass) || mobile == 0l) {
    		Log.error("newPass or mobile cannot be null");
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		return false;
    	}
    	try {
    		Long userId= mobileToUserIdIndex.get(mobile);
    		if(userId == null) {
    			Log.error("userId cannot be null,mobile is {}",mobile);
    			DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
    			return false;
    		}
	    	User user = userMapper.getUserById(userId);
	    	if(user == null) {
	    		Log.error("user cannot be null,userId is {}",userId);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	    		return false;
	    	}
	    	Map<String, Object> params = new HashMap<String, Object>();
	        params.put("id", userId);
	        params.put("password", newPass);
	        userMapper.updateUserById(params);
	        return true;
    	}catch(ServiceException e) {
    		throw e;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return false;
    }

    @Override
    public RegisterResp register(String mobile, String diviceToken,boolean force) {
        // 1. check if this mobile is registered.
        // 1.1 return error if force = false;
    	if(!PhoneNumberUtil.isMobileNumber(mobile)) {
    		Log.error("mobile is null,mobile is {}",mobile);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.UNSUPPORTED_PHONE_NUM);
    		return null;
    	}
    	long mobileNumber = Long.valueOf(mobile);
    	User user = userService.getUserByMobile(mobileNumber);
    	if(force == false && user != null) {
    		Log.error("force is false and mobile is registered,mobile is {}",mobile);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_EXIST);
    		return null;
    	}
        // 2. create a new user
        // 3. set mobile to this new user
    	String borrowedUserID = null;
        Boolean success = false;
    	try {
	        CallerInfo ci = TokenHelper.getInstance().parse(diviceToken);
	        if(ci == null) {
	        	Log.error("diviceToken cannot be parsed, diviceToken:{}",diviceToken);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
	        	return null;
	        }
	        RegisterResp registerResp = new RegisterResp();
	        borrowedUserID = userIdPool.borrow();
	        registerResp.uid = Long.valueOf(borrowedUserID);
	        // 5. generate user token
			// 6. add user-device binding
	        registerResp.expire = TokenHelper.generaterExpire();
	        registerResp.token = TokenHelper.generateUserToken(ci.appid, ci.deviceId, registerResp.uid, registerResp.expire,ci.key);
	        //save user device binding
	        UserDeviceBindingDO userDeviceBindingDO = new UserDeviceBindingDO(ci.deviceId, registerResp.uid, String.valueOf(ci.appid),"0");
	        userDeviceBindingMapper.insertUserDeviceBinding(userDeviceBindingDO);
	        UserDO userDo = new UserDO();
	        userDo.setUserStatus(UserStatus.VALID);
	        userDo.setId(Long.valueOf(borrowedUserID));
	        userDo.setMobile(mobileNumber);
	        userMapper.insertUser(userDo);
	     // 4. remove the mobile from old user if mobile is registered and force = true.
	        if(force == true && user != null) {
	        	 // do db update
	            Map<String, Object> param = new HashMap<String, Object>();
	            param.put("id", user.getId());
	            param.put("mobile", "");
	            userMapper.updateUserById(param);
	        }
	        mobileToUserIdIndex.put(mobileNumber,userDo.getId());

            //发送邀请注册消息
            List<InviteInfo> allInviteInfoList=new ArrayList<InviteInfo>();
            HashMap<Long,InviteInfo> inviteInfoMap=inviteeMobileToInviteInfoIndex.get(mobile);
            if(inviteInfoMap!=null){
                for (Long key : inviteInfoMap.keySet()) {
                    InviteInfo inviteInfo=inviteInfoMap.get(key);
                    HashMap<String, Object> msg = new HashMap<String, Object>();
                    msg.put("inviterUserId",inviteInfo.getInviterUserId());
                    msg.put("inviteeMobile",inviteInfo.getInviteeMobile());
                    msg.put("inviteeFolloweeInfoList",inviteInfo.getInviteeFolloweeInfoList());
                    msg.put("relationType",inviteInfo.getRelationType());
                    msg.put("appFrom",inviteInfo.getAppFrom());
                    msg.put("businessBody",inviteInfo.getBusinessBody());
                    allInviteInfoList.add(inviteInfo);
                }
                String jsonMsg=JSON.toJSONString(allInviteInfoList,SerializerFeature.WriteClassName);
                msgSender.sendMessage(jsonMsg, TopicEnum.USER_CONSUME_CREDIT.getTopic(), allInviteInfoList.get(0).getMessageTag());
            }
	        success = true;
	        return registerResp;
    	}catch(ServiceException e) {
    		Log.error("catch ServiceException", e);
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}finally {
            // return or consume id
            try{
                if(success){
                    if(borrowedUserID != null){
                        userIdPool.consume(borrowedUserID);
                    }
                }else{
                    if(borrowedUserID != null){
                        userIdPool.giveback(borrowedUserID);
                    }
                }
            }catch(Throwable t){
                Log.warn(String.format("failed to return/consume id from id pool! userId=%s, sucess=%s",
                        borrowedUserID, success), t);
            }
        }
    	return null;
    }
    
    @Transactional
    @Override
    public LoginResp federateLogin(String type, String code,int appId) {
    	if(StringUtil.isEmpty(code)) {
    		Log.error("type or code is null,type:{},code:{}",type,code);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		return null;
    	}
        //TODO: device model(1. user -> device binding 2. pure device registration)
    	try {
	    	User user = null;
	        LoginResp resp = new LoginResp();
	        if ("wx".equals(type)) {
	                String openid = getWeixinOpenid(code, ConfigUtil.getProperty("com.pajk.weixinAppId"), ConfigUtil.getProperty("com.pajk.weixinAppSecret"));
	                if (openid == null || openid.length() == 0) {
	                	DubboExtProperty.setErrorCode(UserServiceHttpCode.LOGIN_FAILED);
	                    return null;
	                }
	                user = userService.getOrCreateUserByOpenId(openid, ChannelType.WEIXIN);
	                appId=3;//微信登陆，写死
	        }
	        if(user == null) {
            	DubboExtProperty.setErrorCode(UserServiceHttpCode.LOGIN_FAILED);
            	return null;
            }
	        resp.uid = user.getId();
	        resp.expire = TokenHelper.generaterExpire();
	        long deviceId = -1000L;
	        HMacHelper hMacHelper = new HMacHelper(ConfigUtil.getProperty("com.pajk.csrfTokenSecret"));
	        String token = TokenHelper.generateUserToken(appId, deviceId , resp.uid, resp.expire, null);
	        resp.token = Base64Util.encodeToString(hMacHelper.sign(Base64Util.decode(token)));
	        DubboExtProperty.setCookieToken(token);
	    	//activate this binding
	 		/*UserDeviceBindingDO userDeviceBindingDO = new UserDeviceBindingDO(deviceId, resp.uid, String.valueOf(appId),"0");
	 		UserDeviceBindingDO userDeviceBinding = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(resp.uid, deviceId);
	        if(userDeviceBinding == null) {
	         	userDeviceBindingMapper.insertUserDeviceBinding(userDeviceBindingDO);
	        }*/
	        return resp;
    	}catch(RuntimeException e){
        	Log.error("internal exception.", e);
        	DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
    	return null;
    }

    @Override
    @Transactional
    public UpstreamSmsInfo requestSmsPassword(String mobile, String scenario) {  	
    	// scenario: LOGIN / REGISTER
    	// 1. check if mobile is registered
        //    LOGIN: return error if not registered
        //    REGISTER: return error if already registered
    	try {
    		if(!PhoneNumberUtil.isSupportNumber(mobile)) {
    			Log.error("phone number format error,mobile is {}", mobile);
        		DubboExtProperty.setErrorCode(UserServiceHttpCode.UNSUPPORTED_PHONE_NUM);
        		return null;
        	}
	    	// 2. prepare password pair for this mobile
	    	SmsInfo smsInfo = smsPasswordManager.genSmsPasswords(mobile);
	    	// 3. send SMS request to message gateway
	    	SmsMessage smsMessage = new SmsMessage();
	    	smsMessage.setContent("短信验证码："+smsInfo.downstreamPassword);
	    	smsMessage.setMobiles(mobile);
	    	smsService.send("",smsMessage);
	    	// 4. prepare upstreamSmsInfo and return
	    	// TODO need a way to swipe out outdated smsPasswords
	    	UpstreamSmsInfo upstreamSmsInfo = new UpstreamSmsInfo();
	    	upstreamSmsInfo.upstreamSmsPhoneNumber = ConfigUtil.getProperty("com.pajk.upstreamMobileNumber");
	    	upstreamSmsInfo.smsContent = smsInfo.upstreamPassword;
	        return upstreamSmsInfo;
    	}catch(ServiceException e) {
    		Log.error("catch ServiceException",e);
    	}catch(RuntimeException e){
        	Log.error("internal exception.", e);
        	DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
    	return null;
    			
    }

    @Transactional
    @Override
    public LoginResp smsPasswordLogin(String mobile, String deviceToken, String smsPass) {
    	if(StringUtil.isEmpty(smsPass) || StringUtil.isEmpty(deviceToken) || StringUtil.isEmpty(mobile)) {
    		Log.error("smsPass or deviceToken or mobile cannot be null!,smsPass:{},mobile:{},deviceToken:{}",smsPass,mobile,smsPass);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		return null;
    	}
    	try {
    		LoginResp loginResp = null;
	    	Long userId = mobileToUserIdIndex.get(Long.valueOf(mobile));
	        if(userId == null){
	        	Log.error("userId cannot be null!,mobile is {}",mobile);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        	return null;
	        }
	    	boolean valid = smsPasswordManager.verifySmsPassword(mobile, smsPass);
	    	// 1. check sms password
	    	if(valid) {
	    		// 2. generate user token
	    		// 3. add user-device binding
	    		loginResp = new LoginResp();
	    		loginResp.uid = userId;
	    		loginResp.expire = TokenHelper.generaterExpire();
	    		CallerInfo callerInfo = TokenHelper.getInstance().parse(deviceToken);
	    		loginResp.token = TokenHelper.generateUserToken(callerInfo.appid,callerInfo.deviceId , userId,loginResp.expire,callerInfo.key);
	            // 4. activate this binding
                mergeUserDeviceBinding(callerInfo.deviceId,userId,callerInfo.appid);
	    	} else {
	    		Log.error("SMS password error,smsPass:{}",smsPass);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.SMS_PASSWORD_ERROR);
	    		return null;
	    	}
	        return loginResp;
    	}catch(RuntimeException e) {
    		Log.error("internal exception.", e);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
    	}
    	return null;
    }

    @Override
    public SafetyQuestions requestSafetyQuestionsForTrustDevice(String mobile,long deviceId) {
    	if(StringUtil.isEmpty(mobile)) {
    		 Log.error("mobile cannot be null");
    		 DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		 return null;
    	}
    	if(!PhoneNumberUtil.isMobileNumber(mobile)) {
			Log.error("phone number format error,mobile is {}", mobile);
    		DubboExtProperty.setErrorCode(UserServiceHttpCode.UNSUPPORTED_PHONE_NUM);
    		return null;
    	}
    	long mobileNumber = Long.valueOf(mobile);
    	try {
	    	User user = userService.getUserByMobile(mobileNumber);
	    	if(user == null){
	    		Log.error("user cannot be null,mobile is {}", mobile);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	    		return null;
	        }
	        Map<String,String> userMap = getFamilyMembers(user.getId());
	        QuestionGenerator questionGenerater = new QuestionGenerator();
			SafetyQuestions safetyQuestions = questionGenerater.getsafetyQuestions(user,userMap);
	    	List<SafetyQuestions.SafetyQuestion> safetyQuestionList = safetyQuestions.questions;
	    	long sumTempleteId=0l;
	    	for(SafetyQuestions.SafetyQuestion e : safetyQuestionList) {
	    		sumTempleteId += (long)e.templeteId;
	    	}
	    	long questionKey = mobileNumber + deviceId + sumTempleteId;
	    	String questionUID = Md5Util.computeToHex(String.valueOf(questionKey).getBytes());
	    	safetyQuestions.questionUID = questionUID;
	    	return safetyQuestions;
    	}catch(ServiceException e){
           Log.error("catch ServiceException");
        }catch(RuntimeException re){
            Log.error("RuntimeException catched.", re);
            DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
    	return null;
    }

    @Override
    public boolean gradeSafetyAnswersForTrustDevice(String mobile, long deviceId, int appId, String safetyAnswers) {
    	try {
	    	SafetyAnswers sa = CommonUtils.jsonToObject(SafetyAnswers.class, safetyAnswers);//json字符串转对象
	    	if(sa == null) {
	    		Log.error("safetyAnswers cannot be parsed JSON,safetyAnswers:{}",safetyAnswers);
                DubboExtProperty.setErrorCode(UserServiceHttpCode.SAFETYEXAM_NOT_QUALIFIED);
	    		return false;
	    	}
	    	long sumTempleteId=0l;
	    	if(sa != null && sa.answers != null) {
	    		for(Answer e : sa.answers) {
	    			sumTempleteId += (long)e.templeteId;
	        	}
	    	}
	    	if(!PhoneNumberUtil.isMobileNumber(mobile)) {
    			Log.error("phone number format error,mobile is {}", mobile);
        		DubboExtProperty.setErrorCode(UserServiceHttpCode.UNSUPPORTED_PHONE_NUM);
        		return false;
        	}
        	Long mobileNumber = Long.valueOf(mobile);

            // TODO[haomin]: should add version to questionKey
	    	long questionKey = mobileNumber + deviceId + sumTempleteId;
	    	String questionUID = Md5Util.computeToHex(String.valueOf(questionKey).getBytes());
	    	//questionUID valid failure，return false
	    	if(!questionUID.equals(sa.questionUID)) {
	    		Log.error("questionUID is not valid,safetyAnswers:{},mobile:{}",safetyAnswers,mobile);
	    		DubboExtProperty.setErrorCode(UserServiceHttpCode.SAFETYEXAM_NOT_QUALIFIED);
	    		return false;
	    	}
	    	User user = userService.getUserByMobile(mobileNumber);
	        if(user == null){
	        	Log.error("user cannot be null,mobile is {}", mobile);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        	return false;
	        }
	        Map<String,String> userMap = getFamilyMembers(user.getId());
            boolean result = AnswerGenerator.getsafetyQuestions(user, sa, userMap);
            if(result) {
                mergeUserDeviceBinding(deviceId,user.getId(),appId);
            }
	        return result;
    	}catch(ServiceException e){
            throw e;
        }catch(RuntimeException re){
        	 Log.error("RuntimeException catched.", re);
        	 DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
    	return false;
    }

    @Override
    public SafetyQuestions requestSafetyQuestionsForUserAquire(long targetUserId) {
		try{
			QuestionGenerator questionGenerater = new QuestionGenerator();
	        User user = userMapper.getUserById(targetUserId);
	        if(user == null){
	        	Log.error("user cannot be null!");
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        	return null;
	        }
	        Map<String,String> userMap = getFamilyMembers(targetUserId);
			SafetyQuestions safetyQuestions = questionGenerater.getsafetyQuestions(user,userMap);
			return safetyQuestions;
        }catch(RuntimeException re){
            Log.error("RuntimeException catched.", re);
            DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
		return null;
    }

    @Override
    public boolean aquireUserBySafetyAnswer(long loginUserId, long targetUserId, String safetyAnswers) {
    	if(safetyAnswers == null ) {
    		 Log.error("safetyAnswers cannot be null");
    		 DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
    		 return false;
		}
		try {
			SafetyAnswers sa = CommonUtils.jsonToObject(SafetyAnswers.class, safetyAnswers);//json字符串转对象
			if(sa == null) {
				Log.error("json string format fail, safetyAnswers is {}",safetyAnswers);
				DubboExtProperty.setErrorCode(UserServiceHttpCode.PARAMETER_ERROR);
				return false;
			}
			 User user = userMapper.getUserById(targetUserId);
	        if(user == null){
	        	Log.error("user cannot be null,targetUserId is {}", targetUserId);
	        	DubboExtProperty.setErrorCode(UserServiceHttpCode.USER_NOT_FOUND);
	        	return false;
	        }
	        Map<String,String> userMap = getFamilyMembers(targetUserId);
	        return AnswerGenerator.getsafetyQuestions(user, sa, userMap);
		}catch(RuntimeException re){
        	Log.error("RuntimeException catched.", re);
        	DubboExtProperty.setErrorCode(UserServiceHttpCode.INTERNAL_SERVER_ERROR);
        }
		return false;
    }
    
    
    /**
     * @param userId 用户主键
     * @return 用户家庭关系成员（最多2个）
     */
    private Map<String,String> getFamilyMembers(long userId) {
    	 List<UserRelation> userRelations = userRelationMapper.getFolloweesByFollowerIdAndType(userId,null);
	        Map<String,String> userMap = new HashMap<String,String>();
	        if(userRelations != null && userRelations.size() > 0) {
	        	for(int i=0;i<userRelations.size();i++) {
	        		List<AccountChannelDO> accountChannels = accountChannelMapper.getAccountChannelByUserId(userRelations.get(i).getFolloweeId());
	        		if(accountChannels != null && accountChannels.size() > 0) {
	        			userMap.put(userRelations.get(i).getFolloweeId().toString(), accountChannels.get(0).getOpenId());
	        		}
	        		if(i==1) break;
	        	}
	        }
    	return userMap;
    }
    
    private String getWeixinOpenid(String code, String appId, String appSecret) {
        try {
            String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
//            String jsonStr = WebRequestUtil.getResponseString(url, null, false);
            
            // 创建URL对象
            URL myURL = new URL(url);
            // 创建HttpsURLConnection对象，并设置其SSLSocketFactory对象
            HttpsURLConnection httpsConn = (HttpsURLConnection) myURL
                    .openConnection();
            // 取得该连接的输入流，以读取响应内容
            InputStreamReader insr = new InputStreamReader(httpsConn
                    .getInputStream());
            // 读取服务器的响应内容并显示
            int respInt = insr.read();
            String jsonStr = "";
            while (respInt != -1) {
            	 jsonStr = jsonStr + (char) respInt;
                respInt = insr.read();
            }
            JSONObject json = JSON.parseObject(jsonStr);
            String error = json.getString("errcode");
            String msg = json.getString("errmsg");
            if (error != null && error.length() > 0) {
                Log.error("get weixin openid failed. code:" + code + "error:" + error + "  " + msg);
            }
            String openid = json.getString("openid");
            return (openid != null && openid.length() > 0) ? openid : null;
        } catch (Exception e) {
        	e.printStackTrace();
        	Log.error("get weixin openid failed. code:" + code, e);
        }
        return null;
    }

    private void mergeUserDeviceBinding(long deviceId,long userId,long appid) {
        // 4. add user-device binding
        UserDeviceBindingDO userDeviceBindingDO = new UserDeviceBindingDO(deviceId, userId, String.valueOf(appid),"0");
        // 5. activate this binding
        Map<String, Object> param = new HashMap<String, Object>();
        param.put("userId", userId);
        param.put("active",1);//set active  is  inactive
        userDeviceBindingMapper.updateUserDeviceBindingByUserId(param);
        UserDeviceBindingDO userDeviceBinding = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(userId, deviceId);
        if(userDeviceBinding == null) {
            userDeviceBindingMapper.insertUserDeviceBinding(userDeviceBindingDO);
        }else {
            //update binding is active and other binding is inactive
            param.put("deviceId", deviceId);
            param.put("active", 0);//set active
            userDeviceBindingMapper.updateUserDeviceBindingByUserIdAndDeviceId(param);
        }
    }

}
